package com.gitee.feizns.dynamic.function;

import com.gitee.feizns.dynamic.Ex;
import com.gitee.feizns.dynamic.stream.Streams;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * 尝试 Catch
 * @author feizns
 * @since 2024/12/12
 */
public class TryCatch<T> {

    /**
     * 供应商
     */
    private final ThrowableSupplier<T> supplier;

    /**
     * 结果
     */
    private T result;

    /**
     * 异常
     */
    private Throwable exception;

    /**
     * 尝试 Catch
     * @param supplier 供应商
     */
    public TryCatch(ThrowableSupplier<T> supplier) {
        this.supplier = supplier;
        try {
            this.result = supplier.get();
        } catch (Throwable e) {
            this.exception = e;
        }
    }

    /**
     * 尝试 Catch
     * @param runnable 可运行
     */
    public TryCatch(ThrowableRunnable runnable) {
        this.supplier = () -> { runnable.run(); return null; };
        try {
            runnable.run();
        } catch (Throwable e) {
            this.exception = e;
        }
    }

    /**
     * 之
     * @param supplier 供应商
     * @return {@link TryCatch}<{@link T}>
     */
    public static <T> TryCatch<T> of(ThrowableSupplier<T> supplier) {
        return new TryCatch<>(supplier);
    }

    /**
     * 之
     * @param runnable 可运行
     * @return {@link TryCatch}<{@link T}>
     */
    public static <T> TryCatch<T> of(ThrowableRunnable runnable) {
        return new TryCatch<>(runnable);
    }

    /**
     * 重试执行的函数
     * @param supplier 供应商
     * @param retryPredicate 返回true则继续重试、返回false则停止重试
     * @return {@link TryCatch}<{@link T}>
     */
    public static <T> TryCatch<T> retry(ThrowableSupplier<T> supplier, Predicate<TryCatch<T>> retryPredicate) {
        TryCatch<T> of;
        do {
            of = of(supplier);
        } while ( retryPredicate.test(of) );
        return of;
    }


    /**
     * 重试执行的函数
     * @param supplier 供应商
     * @param maxRetries 最大重试次数
     * @return {@link TryCatch}<{@link T}>
     */
    public static <T> TryCatch<T> retry(ThrowableSupplier<T> supplier, int maxRetries) {
        AtomicInteger count = new AtomicInteger(-1);
        return retry(() -> {
            count.incrementAndGet();
            return supplier.get();
        }, o -> o.hasException() && count.get() < maxRetries);
    }

    /**
     * 直到成功
     * @param supplier 供应商
     * @return {@link TryCatch}<{@link T}>
     */
    public static <T> T getUntilSuccess(ThrowableSupplier<T> supplier) {
        return retry(supplier, TryCatch::hasException).get();
    }

    /**
     * 重试
     * @return {@link TryCatch}<{@link T}>
     */
    public TryCatch<T> retry() {
        return new TryCatch<T>(supplier);
    }

    /**
     * 重试
     * @param maxRetries 最大重试次数
     * @return {@link TryCatch}<{@link T}>
     */
    public TryCatch<T> retry(int maxRetries) {
        return retry(supplier, maxRetries - 1);
    }

    /**
     * 重试直至成功
     * @return {@link T}
     */
    public T getUntilSuccess() {
        return hasException() ? getUntilSuccess(supplier) : get();
    }

    /**
     * {@link Optional}
     * @return {@link Optional}<{@link T}>
     */
    public Optional<T> optional() {
        return Optional.ofNullable(result);
    }

    /**
     * 获取值
     * @return {@link T }
     */
    public T get() {
        return result;
    }

    /**
     * 获取
     * @param defaultValue 默认值
     * @return {@link T }
     */
    public T orElse(T defaultValue) {
        return result != null ? result : defaultValue;
    }

    /**
     * 获取
     * @param defaultValue 默认值
     * @return {@link T}
     */
    public T orElseGet(Supplier<T> defaultValue) {
        return result != null ? result : defaultValue.get();
    }

    /**
     * 尝试 Catch
     * @param supplier 供应商
     * @return {@link TryCatch}<{@link R}>
     */
    public <R> TryCatch<R> tryCatch(ThrowableFunction<T, R> supplier) {
        return new TryCatch<R>(() -> supplier.apply(result));
    }

    /**
     * 尝试 Catch
     * @param consumer 消费者
     * @return {@link TryCatch}<{@link Void}>
     */
    public TryCatch<Void> tryCatch(ThrowableConsumer<T> consumer) {
        return new TryCatch<>(() -> consumer.accept(result));
    }

    /**
     * 如果存在
     * @param consumer 消费者
     */
    public TryCatch<T> ifPresent(Consumer<T> consumer) {
        if ( result != null )
            consumer.accept(result);
        return this;
    }

    /**
     * 有异常
     * @return boolean
     */
    public boolean hasException() {
        return exception != null;
    }

    /**
     * 有异常
     */
    public TryCatch<T> hasException(Consumer<Throwable> consumer) {
        if ( exception != null )
            consumer.accept(exception);
        return this;
    }

    /**
     * 抛出异常
     */
    public T orElseThrow(Function<Throwable, Throwable> throwableFunction) {
        if ( hasException() )
            throw new RuntimeException(throwableFunction.apply(exception));
        return result;
    }

    /**
     * 抛出异常
     */
    public T orElseThrow(String message) {
        if ( hasException() )
            throw new RuntimeException(message, exception);
        return result;
    }

    /**
     * 抛出异常
     */
    public T orElseThrow() {
        if ( hasException() )
            throw new RuntimeException(exception);
        return result;
    }

    /**
     * 忽略异常
     * @param ignoreClasses 类型列表
     * @return {@link TryCatch}<{@link T}>
     */
    @SafeVarargs
    public final TryCatch<T> ignoreException(Class<? extends Throwable>... ignoreClasses) {
        return hasException(e -> {
            if ( Streams.of(ignoreClasses).noneMatch(clz -> clz.isAssignableFrom(e.getClass())) )
                throw new RuntimeException(e);
        });
    }

    /**
     * 查找异常原因
     * @param courseClass 异常类型
     * @param handler 处理器
     * @return {@link TryCatch }<{@link T }>
     */
    @SuppressWarnings("unchecked")
    public <X extends Throwable> TryCatch<T> findCourse(Class<X> courseClass, Consumer<X> handler) {
        return hasException(e -> {
            Throwable course = Ex.findCourse(e, courseClass);
            if ( course != null )
                handler.accept((X) e);
        });
    }

}